﻿using System;
using System.Globalization;

namespace HK.Core.Utils 
{
	
	/// <summary>
	/// 工具类 : 时间.
	/// </summary>
	public static class UtilsTime
	{

		/// <summary>
		/// 开始时间 - 年
		/// </summary>
		private static readonly int START_YEAR = 1970;
		
		/// <summary>
		/// 开始时间 - 月
		/// </summary>
		private static readonly int START_MONTH = 1;
		
		/// <summary>
		/// 开始时间 - 日
		/// </summary>
		private static readonly int START_DAY = 1;
		
		/// <summary>
		/// 开始时间 - 时
		/// </summary>
		private static readonly int START_HOUR = 0;
		
		/// <summary>
		/// 开始时间 - 分钟
		/// </summary>
		private static readonly int START_MINUTE = 0;
		
		/// <summary>
		/// 开始时间 - 秒
		/// </summary>
		private static readonly int START_SECOND = 0;
		
		/// <summary>
		/// 日期格式
		/// </summary>
		public enum DateFormat
		{
			/// <summary>
			/// 日期格式：YYYY/MM/DD HH:MM:SS.MS
			/// </summary>
			YyyyMmDdHhMmSsMs,
			/// <summary>
			/// 日期格式：YYYY/MM/DD HH:MM:SS
			/// </summary>
			YyyyMmDdHhMmSs,
			/// <summary>
			/// 日期格式：YYYYMMDDHHMMSS
			/// </summary>
			YyyyMmDdHhMmSs_0,
			/// <summary>
			/// 日期格式：YYYY-MM-DDTHH:MM:SSZ<BR/>
			/// * XCode Plist专用格式。如:2021-11-23T09:48:58Z
			/// </summary>
			YyyyMmDdTHhMmSsZ,
			/// <summary>
			/// 日期格式：YYYY/MM/DD HH:MM
			/// </summary>
			YyyyMmDdHhMm,
			/// <summary>
			/// 日期格式：YYYY/MM/DD HH
			/// </summary>
			YyyyMmDdHh,
			/// <summary>
			/// 日期格式：YYYY/MM/DD
			/// </summary>
			YyyyMmDd,
			/// <summary>
			/// 日期格式：YYYY/MM
			/// </summary>
			YyyyMm,
			/// <summary>
			/// 日期格式：MM/DD/YYYY
			/// </summary>
			MmDdYyyy,
			/// <summary>
			/// 日期格式：MM/DD
			/// </summary>
			MmDd,
			/// <summary>
			/// 日期格式：MM/DD HH:MM:SS.Ms
			/// </summary>
			MmDdHhMmSsMs,
			/// <summary>
			/// 日期格式：MM/DD HH:MM:SS
			/// </summary>
			MmDdHhMmSs,
			/// <summary>
			/// 日期格式：MM/DD HH:MM
			/// </summary>
			MmDdHhMm,
			/// <summary>
			/// 日期格式：HH:MM:SS.MS
			/// </summary>
			HhMmSsMs,
			/// <summary>
			/// 日期格式：HH:MM:SS
			/// </summary>
			HhMmSs,
			/// <summary>
			/// 日期格式：HH:MM
			/// </summary>
			HhMm,
			/// <summary>
			/// 日期格式：MM:SS.MS
			/// </summary>
			MmSsMs,
			/// <summary>
			/// 日期格式：MM:SS
			/// </summary>
			MmSs,
			/// <summary>
			/// 日期格式：SS.MS
			/// </summary>
			SsMs,
			/// <summary>
			/// 日期格式：SS
			/// </summary>
			Ss
		}

		/// <summary>
		/// 标准时间
		/// </summary>
		private static DateTime _stDateTime;

		/// <summary>
		/// 初始化标志位
		/// </summary>
		private static bool _initialized = false;

		/// <summary>
		/// 转换日期格式
		/// </summary>
		/// <param name="iFormat"></param>
		/// <returns></returns>
		private static string ConvertDateFormat(DateFormat iFormat)
		{
			var format = "yyyy/MM/dd HH:MM";
			switch (iFormat)
			{
				case DateFormat.YyyyMmDdHhMmSsMs:
					format = "yyyy/MM/dd HH:mm:ss.ff";
					break;
				case DateFormat.YyyyMmDdHhMmSs:
					format = "yyyy/MM/dd HH:mm:ss";
					break;
				case DateFormat.YyyyMmDdHhMmSs_0:
					format = "yyyyMMddHHmmss";
					break;
				case DateFormat.YyyyMmDdTHhMmSsZ:
					format = "yyyy-MM-ddTHH:mm:ssZ";
					break;
				case DateFormat.YyyyMmDdHhMm:
					format = "yyyy/MM/dd HH:mm";
					break;
				case DateFormat.YyyyMmDdHh:
					format = "yyyy/MM/dd HH";
					break;
				case DateFormat.YyyyMmDd:
					format = "yyyy/MM/dd";
					break;
				case DateFormat.YyyyMm:
					format = "yyyy/MM";
					break;
				case DateFormat.MmDdYyyy:
					format = "MM/dd/yyyy";
					break;
				case DateFormat.MmDd:
					format = "MM/dd";
					break;
				case DateFormat.MmDdHhMmSsMs:
					format = "MM/dd HH:mm:ss.ff";
					break;
				case DateFormat.MmDdHhMmSs:
					format = "MM/dd HH:mm:ss";
					break;
				case DateFormat.MmDdHhMm:
					format = "MM/dd HH:mm";
					break;
				case DateFormat.HhMmSsMs:
					format = "HH:mm:ss.ff";
					break;
				case DateFormat.HhMmSs:
					format = "HH:mm:ss";
					break;
				case DateFormat.HhMm:
					format = "HH:mm";
					break;
				case DateFormat.MmSsMs:
					format = "mm:ss.ff";
					break;
				case DateFormat.MmSs:
					format = "mm:ss";
					break;
				case DateFormat.SsMs:
					format = "ss.ff";
					break;
				case DateFormat.Ss:
					format = "ss";
					break;
			}
			return format;
		}
		
		/// <summary>
		/// 判断是否在开启日期之前.
		/// </summary>
		/// <param name="iStartDate">开始日期.</param>
		/// <param name="iFormat">日期格式（默认格式 : YYYY/MM/DD HH:MM:SS）.</param>
		/// <returns><c>true</c>, 时, <c>false</c> 不是.</returns>
		public static bool IsBeforeDate (string iStartDate, DateFormat iFormat = DateFormat.YyyyMmDdHhMmSs) 
		{
			var now = DateTime.Now;
			if (string.IsNullOrEmpty(iStartDate) || "-".Equals(iStartDate)) return false;
			var dtFormat = new DateTimeFormatInfo
			{
				ShortDatePattern = ConvertDateFormat(iFormat)
			};

			var start = Convert.ToDateTime (iStartDate, dtFormat);
			return now.CompareTo (start) < 0;
		}

		/// <summary>
		/// 检测活动有效日期.
		/// </summary>
		/// <param name="iStartDate">开始日期.</param>
		/// <param name="iEndDate">结束日期.</param>
		/// <param name="iFormat">日期格式（默认格式 : YYYY/MM/DD HH:MM:SS）.</param>
		/// <returns>true : 有效期内; false : 有效期外.</returns>
		public static bool CheckDate (
			string iStartDate, string iEndDate, 
			DateFormat iFormat = DateFormat.YyyyMmDdHhMmSs) 
		{
			var now = DateTime.Now;
			var dtCheckFormat = new DateTimeFormatInfo { ShortDatePattern = ConvertDateFormat(iFormat) };
			
			if (false == string.IsNullOrEmpty (iStartDate) &&
				false == "-".Equals (iStartDate)) {
				
				var start = Convert.ToDateTime (iStartDate, dtCheckFormat);
				if (now.CompareTo (start.ToUniversalTime().Date) < 0) 
				{
					return false;
				}
			}

			if (string.IsNullOrEmpty(iEndDate) || "-".Equals(iEndDate)) return true;
			var end = Convert.ToDateTime (iEndDate, dtCheckFormat);
			if (end.ToUniversalTime().Date.CompareTo (now) <= 0) 
			{
				return false;
			}

			return true;
		}

		/// <summary>
		/// 取得标准时间
		/// <para>备注</para>
		/// <para>* 以1970年1月1日为计算当前时间得标准基准时间</para>
		/// <para>* UTC时间</para>
		/// </summary>
		/// <param name="iKind">DateTimeKind</param>
		/// <returns>当前时间</returns>
		public static DateTime GetStdDataTime(DateTimeKind iKind = DateTimeKind.Utc)
		{
			if(_initialized) return _stDateTime;
			
			_stDateTime = new DateTime(
				START_YEAR, START_MONTH, START_DAY, 
				START_HOUR, START_MINUTE, START_SECOND, 
				iKind);
			_initialized = true;
			return _stDateTime;
		}

		/// <summary>
		/// 取得当前日期和时间的刻度数
		/// <para>备注</para>
		/// <para>* 此属性的值为自 1970 年 1 月 1 日午夜 12:00 以来所经过得毫秒值</para>
		/// <para>* 返回为UTC时间</para>
		/// </summary>
		/// <returns>当前日期和时间的刻度数(单位:毫秒数)</returns>
		public static long GetCurDateTimeTicks()
		{
			return GetDateTimeTicks(DateTime.UtcNow);
		}
		
		/// <summary>
		/// 取得当前日期和时间的刻度数
		/// <para>备注</para>
		/// <para>* 此属性的值为自 1970 年 1 月 1 日午夜 12:00 以来所经过得毫秒值</para>
		/// <para>* 返回为本地时间</para>
		/// </summary>
		/// <returns>当前日期和时间的刻度数(单位:毫秒数)</returns>
		public static long GetCurDateTimeLocalTicks()
		{
			return GetDateTimeTicks(DateTime.UtcNow, false);
		}

		/// <summary>
		/// 取得当前日期和时间的刻度数
		/// <para>备注</para>
		/// <para>* 此属性的值为自 1970 年 1 月 1 日午夜 12:00 以来所经过得毫秒值</para>
		/// </summary>
		/// <param name="iDateTime">时间</param>
		/// <param name="iFormat">日期格式（默认格式 : YYYY/MM/DD HH:MM:SS）.</param>
		/// <returns>当前日期和时间的刻度数(单位:毫秒数)</returns>
		public static long GetDateTimeTicks(string iDateTime, 
			DateFormat iFormat = DateFormat.YyyyMmDdHhMmSsMs)
		{
			var dtFormat = new DateTimeFormatInfo { ShortDatePattern = ConvertDateFormat( iFormat ) };
			var start = Convert.ToDateTime (iDateTime, dtFormat);
			return GetDateTimeTicks(start.ToUniversalTime());
		}
		
		/// <summary>
		/// 取得当前日期和时间的刻度数
		/// <para>备注</para>
		/// <para>* 此属性的值为自 1970 年 1 月 1 日午夜 12:00 以来所经过得毫秒值</para>
		/// </summary>
		/// <param name="iDateTime">时间</param>
		/// <param name="iUtc">UTC时间标志位</param>
		/// <returns>当前日期和时间的刻度数(单位:毫秒数)</returns>
		public static long GetDateTimeTicks(DateTime iDateTime, bool iUtc = true)
		{
			// 取得标准时间
			var stdDataTime = GetStdDataTime(iUtc ? DateTimeKind.Utc : DateTimeKind.Local);
			if (iUtc)
			{
				var utcTime = iDateTime.ToUniversalTime().Ticks - stdDataTime.ToUniversalTime().Ticks;
				return ConvertToMilliSecond(utcTime);
			}
			
			var unUtcTime = iDateTime.ToLocalTime().Ticks - stdDataTime.ToLocalTime().Ticks;
			return ConvertToMilliSecond(unUtcTime);
		}

		/// <summary>
		/// 转换格式<BR/>
		/// * 备注:时间戳为自 1970 年 1 月 1 日午夜 12:00 以来所经过得毫秒值
		/// </summary>
		/// <param name="iTimeTicks">时间戳(单位：毫秒)</param>
		/// <param name="iFormat">日期格式（默认格式 : YYYY/MM/DD HH:MM:SS）.</param>
		/// <param name="iUtc">UTC时间标志位</param>
		/// <param name="iDisplayUtc">时间显示是否为Utc</param>
		/// <returns>日期</returns>
		public static string ConvertByFormat(long iTimeTicks, DateFormat iFormat = DateFormat.YyyyMmDdHhMmSs, 
			bool iUtc = true, bool iDisplayUtc = false)
		{
			if (0L >= iTimeTicks) return "Invalid";
			
			var stDateTime = GetStdDataTime(iUtc ? DateTimeKind.Utc : DateTimeKind.Local);
			var nowDateTime = stDateTime.AddMilliseconds(iTimeTicks);
			if (!iDisplayUtc)
			{
				nowDateTime = nowDateTime.ToLocalTime();
			}
			var format = ConvertDateFormat(iFormat);
			return nowDateTime.ToString(format);
		}
		
		/// <summary>
		/// 转换到毫秒值
		/// </summary>
		/// <param name="iValue">值(单位：100 毫微秒)</param>
		/// <returns>毫秒值</returns>
		private static long ConvertToMilliSecond(long iValue)
		{
			return iValue / 10000;
		}

		/// <summary>
		/// 转换到秒
		/// </summary>
		/// <param name="iTimeTick">时间Ticks(单位:毫秒)</param>
		/// <returns>换到秒</returns>
		public static float ConvertToSecond(long iTimeTick)
		{
			return iTimeTick / 1000.0f;
		}

		/// <summary>
		/// 转换到分钟
		/// </summary>
		/// <param name="iTimeTick">时间Ticks(单位:毫秒)</param>
		/// <returns>换到分钟</returns>
		public static float ConvertToMinute(long iTimeTick)
		{
			return iTimeTick / 60000.0f;
		}

		/// <summary>
		/// 转换到小时
		/// </summary>
		/// <param name="iTimeTick">时间Ticks(单位:毫秒)</param>
		/// <returns>换到小时</returns>
		public static float ConvertToHour(long iTimeTick)
		{
			var minuteTicks = iTimeTick % 3600000;
			var hour = (iTimeTick - minuteTicks) / 3600000.0f;

			var secondTicks = minuteTicks % 60000;
			var minute = (minuteTicks - secondTicks) / 60000.0f;

			var second = (minuteTicks - secondTicks) / 1000.0f;
			return hour + minute + second;
		}
		
		/// <summary>
		/// 取得两个时间刻度数之间的间隔
		/// <para>  备注:此属性的值为自 1970 年 1 月 1 日午夜 12:00 以来所经过得毫秒值</para>
		/// </summary>
		/// <param name="iStart">开始时间(单位 : 毫秒)</param>
		/// <param name="iCurrent">现在时间(单位 : 毫秒)</param>
		/// <returns>两个时间刻度数之间的间隔(单位 : 毫秒)</returns>
		public static long GetDeltaDateTimeTicks(long iStart, long iCurrent)
		{
			var deltaTime = iCurrent - iStart;
			deltaTime = (1 >= deltaTime) ? 0 : deltaTime;
			return deltaTime;
		}

		/// <summary>
		/// 取得表示用变化时间<BR/>
		/// * 会根据时间的大小自动变化时间单位<BR/>
		/// * 输出格式: XX h XX m XX s XX ms
		/// </summary>
		/// <param name="iStart">开始时间(单位：毫微秒)</param>
		/// <param name="iCurrent">现在时间(单位：毫微秒)</param>
		/// <param name="iIsShort">剪短表示(true:仅表示当前最大单位时间戳)</param>
		/// <returns>两个时间刻度数之间的间隔(单位:ms/s/m/h)</returns>
		public static string GetDisplayDeltaTime(long iStart, long iCurrent, bool iIsShort = true)
		{
			return GetDisplayDeltaTime(iCurrent - iStart, iIsShort);
		}
		
		/// <summary>
		/// 取得表示用变化时间
		/// <para>* 会根据时间的大小自动变化时间单位</para>
		/// <para>* 输出格式: XX h XX m XX s XX ms</para>
		/// </summary>
		/// <param name="iTime">时间(单位：毫秒)</param>
		/// <param name="iIsShort">剪短表示(true:仅表示当前最大单位时间戳)</param>
		/// <returns>两个时间刻度数之间的间隔(单位:ms/s/m/h)</returns>
		public static string GetDisplayDeltaTime(long iTime, bool iIsShort = true)
		{
			var curValue = GetCurDateTimeTicks();
			var totalValue = curValue - iTime;
			return ConvertToDisplayTime(totalValue, iIsShort);
		}

		/// <summary>
		/// 取得表示用变化时间
		/// <para>* 会根据时间的大小自动变化时间单位</para>
		/// <para>* 输出格式: XX h XX m XX s XX ms</para>
		/// </summary>
		/// <param name="iTimeValue">时间戳(单位：毫秒数)</param>
		/// <param name="iIsShort">简短表示(true:仅表示当前最大单位时间戳)</param>
		/// <param name="iDepth">显示深度</param>
		/// <returns>两个时间刻度数之间的间隔(单位:ms/s/m/h)</returns>
		public static string ConvertToDisplayTime(long iTimeValue, bool iIsShort = true, int iDepth = 0)
		{
			if (0L <= iTimeValue && iTimeValue <= 999L)
			{
				return $"{iTimeValue}ms";
			}

			// 1s ~ 59.9s
			if (1000L <= iTimeValue && iTimeValue <= 59999L)
			{
				// 取得毫秒数
				var millTmp = iTimeValue % 1000;
				// 取得秒数
				var sec = (iTimeValue - millTmp) / 1000;
				if (iIsShort) return $"{sec}s";
				return 0 >= iDepth ? $"{sec}s {millTmp}ms" : 
					ConvertToDisplayTime(iDepth, -1, -1, sec, millTmp);
			}

			// 1min ~ 59.9min 
			if (60000L <= iTimeValue && iTimeValue <= 3599999L)
			{
				// 取得毫秒数
				var millTmp = iTimeValue % 1000;
				// 取得秒数
				var sec = (iTimeValue - millTmp) / 1000;
				sec = sec % 60;
				// 取得分钟数
				var min = (iTimeValue - millTmp - sec * 60) / (60 * 1000);
				if (iIsShort) return $"{min}m";
				return 0 >= iDepth ? $"{min}m {sec}s {millTmp}ms" : 
					ConvertToDisplayTime(iDepth, -1, min, sec, millTmp);
			}
			// >= 1h
			else
			{
				// 取得毫秒数
				var millTmp = iTimeValue % 1000;
				// 取得秒数
				var sec = (iTimeValue - millTmp) / 1000;
				sec = sec % 60;
				// 取得分钟数
				var min = (iTimeValue - millTmp - sec * 60) / (60 * 1000);
				min = min % 60;
				// 取得小时数
				var hour = (iTimeValue - millTmp - sec * 60 - min * 60 * 60) / (60 * 60 * 1000);
				if (iIsShort) return $"{hour}h";
				
				return 0 >= iDepth ? $"{hour}h {min}m {sec}s {millTmp}ms" :
					ConvertToDisplayTime(iDepth, hour, min, sec, millTmp);
			}
		}

		private static string ConvertToDisplayTime(
			int iDepth, long iHour = -1L, long iMinute = -1L, 
			long iSecond = -1L, long iMilliSecond = -1L)
		{
			var timeTxt = string.Empty;
			switch (iDepth)
			{
				case 1:
				{
					if (-1L != iHour)
					{
						timeTxt = $"{iHour}h";
						break;
					}
					if (-1L != iMinute)
					{
						timeTxt = $"{iMinute}m";
						break;
					}
					if (-1L != iSecond)
					{
						timeTxt = $"{iSecond}s";
						break;
					}
					if (-1L != iMilliSecond)
					{
						timeTxt = $"{iMilliSecond}ms";
						break;
					}
				}
					break;
				case 2:
				{
					var dCount = 0;
					if (-1L != iHour)
					{
						timeTxt = $"{iHour}h";
						++dCount;
					}
					if (-1L != iMinute)
					{
						timeTxt = string.IsNullOrEmpty(timeTxt) ? $"{iMinute}m" : $"{timeTxt} {iMinute}m";
						++dCount;
					}

					if (2 <= dCount)
					{
						break;
					}
					if (-1L != iSecond)
					{
						timeTxt = string.IsNullOrEmpty(timeTxt) ? $"{iSecond}s" : $"{timeTxt} {iSecond}s";
						++dCount;
					}

					if (2 <= dCount)
					{
						break;
					}
					if (-1L != iMilliSecond)
					{
						timeTxt = string.IsNullOrEmpty(timeTxt) ? $"{iMilliSecond}ms" : $"{timeTxt} {iMilliSecond}ms";
						++dCount;
					}
				}
					break;
				case 3:
				{
					var dCount = 0;
					if (-1L != iHour)
					{
						timeTxt = $"{iHour}h";
						++dCount;
					}
					if (-1L != iMinute)
					{
						timeTxt = string.IsNullOrEmpty(timeTxt) ? $"{iMinute}m" : $"{timeTxt} {iMinute}m";
						++dCount;
					}
					if (-1L != iSecond)
					{
						timeTxt = string.IsNullOrEmpty(timeTxt) ? $"{iSecond}s" : $"{timeTxt} {iSecond}s";
						++dCount;
					}
					if (3 <= dCount)
					{
						break;
					}
					if (-1L != iMilliSecond)
					{
						timeTxt = string.IsNullOrEmpty(timeTxt) ? $"{iMilliSecond}ms" : $"{timeTxt} {iMilliSecond}ms";
						++dCount;
					}
				}
					break;
				case 4:
				{
					if (-1L != iHour)
					{
						timeTxt = $"{iHour}h";
					}
					if (-1L != iMinute)
					{
						timeTxt = string.IsNullOrEmpty(timeTxt) ? $"{iMinute}m" : $"{timeTxt} {iMinute}m";
					}
					if (-1L != iSecond)
					{
						timeTxt = string.IsNullOrEmpty(timeTxt) ? $"{iSecond}s" : $"{timeTxt} {iSecond}s";
					}
					if (-1L != iMilliSecond)
					{
						timeTxt = string.IsNullOrEmpty(timeTxt) ? $"{iMilliSecond}ms" : $"{timeTxt} {iMilliSecond}ms";
					}
				}
					break;
			}

			return timeTxt;
		}
	}
}
